import CBusUtils::*;
import Register::*;

import Trace::*;
import MemTypes::*;
import IProc::*;
import ProcTypes::*;
import FPGATypes::*;
import RegFile::*;
import FIFO::*;
import BFIFO::*;
import Assert::*;

import GetPut::*;
import GetPutExt::*;
import ClientServer::*;
import CBus::*;


interface IProc;

  // Interface for testrig
  interface Put#(Bit#(8)) testrig_fromhost;
  interface Get#(Bit#(8)) testrig_tohost;

  // Interface from processor to caches
  interface Client#(DataReq,DataResp) dmem_client;
  interface Client#(InstReq,InstResp) imem_client;

  // Interface for enabling/disabling statistics on the rest of the core
  interface Get#(Bool) statsEn_get;

endinterface


typedef enum { PCgen, Exec, Writeback } Stage deriving(Eq,Bits);

//-----------------------------------------------------------
// Register file module
//-----------------------------------------------------------

interface RFile;
   method Action   wr( Rindx rindx, Bit#(32) data );
   method Bit#(32) rd1( Rindx rindx );
   method Bit#(32) rd2( Rindx rindx );
endinterface

module mkRFile( RFile );
   
   RegFile#(Rindx,Bit#(32)) rfile <- mkRegFileFull();
   
   method Action wr( Rindx rindx, Bit#(32) data );
      rfile.upd( rindx, data );
   endmethod
   
   method Bit#(32) rd1( Rindx rindx );
      return ( rindx == 0 ) ? 0 : rfile.sub(rindx);
   endmethod
   
   method Bit#(32) rd2( Rindx rindx );
      return ( rindx == 0 ) ? 0 : rfile.sub(rindx);
   endmethod

endmodule

//-----------------------------------------------------------
// Helper functions
//-----------------------------------------------------------

function Bit#(32) slt( Bit#(32) val1, Bit#(32) val2 );
   return zeroExtend( pack( signedLT(val1,val2) ) );
endfunction

function Bit#(32) sltu( Bit#(32) val1, Bit#(32) val2 );
   return zeroExtend( pack( val1 < val2 ) );
endfunction

function Bit#(32) rshft( Bit#(32) val );
   return zeroExtend(val[4:0]);
endfunction

//-----------------------------------------------------------
// Reference processor
//-----------------------------------------------------------


module  [ModWithCBus#(AvalonAddressWidth,AvalonDataWidth)] mkProc( IProc );

   //-----------------------------------------------------------
   // State

	// wjun
	Reg#(Bit#(32)) exec_cycles <- mkReg(8);

   // Standard processor state
   
   Reg#(Addr)  pc    <- mkReg(32'h00001000);
   Reg#(Stage) stage <- mkReg(PCgen);
   RFile       rf    <- mkRFile;

   // Coprocessor state
   
   Reg#(Bit#(8)) cp0_tohost   <- mkReg(0);
   mkCBusWideRegRW(valueof(ToHostRegAddr),cp0_tohost);
   Reg#(Bit#(8)) cp0_fromhost <- mkReg(0);
   mkCBusWideRegRW(valueof(FromHostRegAddr),cp0_fromhost);
   Reg#(Bool)    cp0_statsEn  <- mkReg(False);
   mkCBusWideRegRW(valueof(StatsEnRegAddr),cp0_statsEn);
   // Memory request/response state
   
   FIFO#(InstReq)  instReqQ    <- mkBFIFO1();
   FIFO#(InstResp) instRespQ   <- mkFIFO();

   FIFO#(DataReq)  dataReqQ    <- mkBFIFO1();
   FIFO#(DataResp) dataRespQ   <- mkFIFO();

   // Statistics state

   Reg#(Int#(25))  num_cycles  <- mkReg(25'h0);
   Reg#(Int#(25))  num_inst    <- mkReg(25'h0);

   //-----------------------------------------------------------
   // Rules

   let pc_plus4 = pc + 4;

   rule pcgen ( stage == PCgen );
      traceTiny("pc",pc);
      traceTiny("stage","P");

			// wjun
			if ( exec_cycles > 0 ) {
		     instReqQ.enq( LoadReq{ addr:pc, tag:0 } );
				 exec_cycles <= exec_cycles - 1;
			}

      stage   <= Exec;
   endrule

   rule exec ( stage == Exec );

      // Some abbreviations
      let sext = signExtend;
      let zext = zeroExtend;
      let sra  = signedShiftRight;
      
      // Get the instruction
      
      instRespQ.deq();
      Instr inst 
      = case ( instRespQ.first() ) matches
	   tagged LoadResp  .ld : return unpack(ld.data);
	   tagged StoreResp .st : return ?;
	endcase;

      // Some default variables
      Stage next_stage = PCgen;
      Addr  next_pc    = pc_plus4;

      // Tracing
      traceTiny("stage","X");
      traceTiny("exInstTiny",inst);
      traceFull("exInstFull",inst);

      case ( inst ) matches

	 // -- Memory Ops ------------------------------------------------      

	 tagged LW .it : 
	    begin
               Addr addr = rf.rd1(it.rbase) + sext(it.offset);
               dataReqQ.enq( LoadReq{ addr:addr, tag:zeroExtend(it.rdst) } );
               next_stage = Writeback;
	    end

	 tagged SW .it : 
	    begin
               Addr addr = rf.rd1(it.rbase) + sext(it.offset);
               dataReqQ.enq( StoreReq{ tag:0, addr:addr, data:rf.rd1(it.rsrc) } );
               next_stage = Writeback;
	    end

	 // -- Simple Ops ------------------------------------------------      

	 tagged ADDIU .it : rf.wr( it.rdst, rf.rd1(it.rsrc) + sext(it.imm) );
	 tagged SLTI  .it : rf.wr( it.rdst, slt( rf.rd1(it.rsrc), sext(it.imm) ) );
	 tagged SLTIU .it : rf.wr( it.rdst, sltu( rf.rd1(it.rsrc), sext(it.imm) ) );
	 tagged ANDI  .it : 
	    begin
	       Bit#(32) zext_it_imm = zext(it.imm);
	       rf.wr( it.rdst, rf.rd1(it.rsrc) & zext_it_imm );
	    end
	 tagged ORI   .it : 
	    begin
	       Bit#(32) zext_it_imm = zext(it.imm);
	       rf.wr( it.rdst, rf.rd1(it.rsrc) | zext_it_imm );
	    end
	 tagged XORI  .it : 
	    begin
	       Bit#(32) zext_it_imm = zext(it.imm);
	       rf.wr( it.rdst, rf.rd1(it.rsrc) ^ zext_it_imm );
	    end
	 tagged LUI   .it : 
	    begin
	       Bit#(32) zext_it_imm = zext(it.imm);
	       rf.wr( it.rdst, (zext_it_imm << 32'd16) );
	    end
	 
	 tagged SLL   .it : 
	    begin
	       Bit#(32) zext_it_shamt = zext(it.shamt);
	       rf.wr( it.rdst, rf.rd1(it.rsrc) << zext_it_shamt );
	    end
	 tagged SRL   .it : 
	    begin
	       Bit#(32) zext_it_shamt = zext(it.shamt);
	       rf.wr( it.rdst, rf.rd1(it.rsrc) >> zext_it_shamt );
	    end
	 tagged SRA   .it : 
	    begin
	       Bit#(32) zext_it_shamt = zext(it.shamt);
	       rf.wr( it.rdst, sra( rf.rd1(it.rsrc), zext_it_shamt ));
	    end
	 tagged SLLV  .it : rf.wr( it.rdst, rf.rd1(it.rsrc) << rshft(rf.rd2(it.rshamt)) );
	 tagged SRLV  .it : rf.wr( it.rdst, rf.rd1(it.rsrc) >> rshft(rf.rd2(it.rshamt)) );
	 tagged SRAV  .it : rf.wr( it.rdst, sra( rf.rd1(it.rsrc), rshft(rf.rd2(it.rshamt)) ) );
	 tagged ADDU  .it : rf.wr( it.rdst, rf.rd1(it.rsrc1) + rf.rd2(it.rsrc2) );
	 tagged SUBU  .it : rf.wr( it.rdst, rf.rd1(it.rsrc1) - rf.rd2(it.rsrc2) );
	 tagged AND   .it : rf.wr( it.rdst, rf.rd1(it.rsrc1) & rf.rd2(it.rsrc2) );
	 tagged OR    .it : rf.wr( it.rdst, rf.rd1(it.rsrc1) | rf.rd2(it.rsrc2) );
	 tagged XOR   .it : rf.wr( it.rdst, rf.rd1(it.rsrc1) ^ rf.rd2(it.rsrc2) );
	 tagged NOR   .it : rf.wr( it.rdst, ~(rf.rd1(it.rsrc1) | rf.rd2(it.rsrc2)) );
	 tagged SLT   .it : rf.wr( it.rdst, slt( rf.rd1(it.rsrc1), rf.rd2(it.rsrc2) ) );
	 tagged SLTU  .it : rf.wr( it.rdst, sltu( rf.rd1(it.rsrc1), rf.rd2(it.rsrc2) ) );

	 // -- Branches --------------------------------------------------

	 tagged BLEZ  .it : 
            if ( signedLE( rf.rd1(it.rsrc), 0 ) )
               next_pc = pc_plus4 + (sext(it.offset) << 2);

	 tagged BGTZ  .it : 
            if ( signedGT( rf.rd1(it.rsrc), 0 ) ) 
               next_pc = pc_plus4 + (sext(it.offset) << 2);

	 tagged BLTZ  .it : 
            if ( signedLT( rf.rd1(it.rsrc), 0 ) )
               next_pc = pc_plus4 + (sext(it.offset) << 2);

	 tagged BGEZ  .it : 
            if ( signedGE( rf.rd1(it.rsrc), 0 ) )
               next_pc = pc_plus4 + (sext(it.offset) << 2);

      tagged BEQ   .it : 
        if ( rf.rd1(it.rsrc1) == rf.rd2(it.rsrc2) )
          next_pc = pc_plus4 + (sext(it.offset) << 2);

      tagged BNE   .it : 
        if ( rf.rd1(it.rsrc1) != rf.rd2(it.rsrc2) )
          next_pc = pc_plus4 + (sext(it.offset) << 2);
      
      // -- Jumps -----------------------------------------------------
      
      tagged J     .it : 
        next_pc = { pc_plus4[31:28], it.target, 2'b0 };
      
      tagged JR    .it : 
        next_pc = rf.rd1(it.rsrc);

      tagged JAL   .it : 
       begin
        rf.wr( 31, pc_plus4 );
        next_pc = { pc_plus4[31:28], it.target, 2'b0 };
       end

      tagged JALR  .it : 
       begin
        rf.wr( it.rdst, pc_plus4 );
        next_pc = rf.rd1(it.rsrc);
       end

      // -- Cop0 ------------------------------------------------------
      
      tagged MTC0  .it : 
        case ( it.cop0dst )
          5'd10 : cp0_statsEn <= unpack(truncate(rf.rd1(it.rsrc)));
          5'd21 : cp0_tohost  <= truncate(rf.rd1(it.rsrc));
          default : 
            $display( " RTL-ERROR : %m : Illegal MTC0 cop0dst register!" );
        endcase

      tagged MFC0  .it : 
        case ( it.cop0src )
          5'd10 : rf.wr( it.rdst, zext(pack(cp0_statsEn)) );
          5'd20 : rf.wr( it.rdst, zext(cp0_fromhost) );
          5'd21 : rf.wr( it.rdst, zext(cp0_tohost)   );
          default : 
            $display( " RTL-ERROR : %m : Illegal MFC0 cop0src register!" );
        endcase

      // -- Illegal ---------------------------------------------------

      default : 
        $display( " RTL-ERROR : %m : Illegal instruction !" );

    endcase

    stage    <= next_stage;
    pc       <= next_pc;

    if ( cp0_statsEn )
      num_inst <= num_inst + 1;

  endrule

  rule writeback ( stage == Writeback );
    traceTiny("stage","W");

    dataRespQ.deq();
    case ( dataRespQ.first() ) matches
      tagged LoadResp .ld  : rf.wr( truncate(ld.tag), ld.data );	
      tagged StoreResp .st : noAction;
    endcase

    stage <= PCgen;
  endrule

  rule inc_num_cycles;
    if ( cp0_statsEn )
      num_cycles <= num_cycles + 1;
  endrule

  //-----------------------------------------------------------
  // Methods

  interface Client imem_client;
    interface Get request  = fifoToGet(instReqQ);
    interface Put response = fifoToPut(instRespQ);
  endinterface

  interface Client dmem_client;
    interface Get request  = fifoToGet(dataReqQ);
    interface Put response = fifoToPut(dataRespQ);
  endinterface

  interface Get testrig_tohost   = regToGet(cp0_tohost);
  interface Put testrig_fromhost = regToPut(cp0_fromhost);
  interface Get statsEn_get      = regToGet(cp0_statsEn);

endmodule

